﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Cjc.Calculation.ExpressionEngine.Excel.Runtime.Operations;
using Cjc.Calculation.ExpressionEngine.Excel.Runtime.Types;
using System.Linq.Expressions;
using Microsoft.Scripting.Actions;
using Microsoft.Scripting.Actions.Calls;
using Microsoft.Scripting.Ast;
using Microsoft.Scripting.Generation;
using Microsoft.Scripting.Runtime;

namespace Cjc.Calculation.ExpressionEngine.Excel.Runtime.Binding
{
	public class ExcelBinder : DefaultBinder
	{
		private static Dictionary<Type, Type[]> extensionTypes;
		private static Type[] types = new[] { typeof( Range ), typeof( CellId ), typeof( Cell ) };

		/// <summary>
		/// Initializes the <see cref="ExcelBinder"/> class.
		/// </summary>
		static ExcelBinder()
		{
			extensionTypes = new Dictionary<Type, Type[]>();
			extensionTypes[ typeof( decimal ) ] = new[] { typeof( DecimalOps ) };
			extensionTypes[ typeof( double ) ] = new[] { typeof( DoubleOps ), typeof( DecimalOps ) };
			extensionTypes[ typeof( long ) ] = new[] { typeof( BigIntOps ), typeof( DecimalOps ) };
			extensionTypes[ typeof( int ) ] = new[] { typeof( Int32Ops ), typeof( DecimalOps ) };
            extensionTypes[ typeof(bool) ] = new[] { typeof(Int32Ops), typeof(DecimalOps) };
            extensionTypes[ typeof(string) ] = new[] { typeof(StringOps) };
			extensionTypes[ typeof( DynamicNull ) ] = new[] { typeof( NoneTypeOps ) };
		}

		/// <summary>
		/// Initializes a new instance of the <see cref="ExcelBinder"/> class.
		/// </summary>
        public ExcelBinder() : base()
        {
		}

		/// <summary>
		/// Gets the extension types that provide our user-defined conversions and operations
		/// </summary>
		/// <param name="t">The t.</param>
		/// <returns></returns>
		public override IList<Type> GetExtensionTypes( Type t )
		{
			IEnumerable<Type> types = new[] { t }.Union( base.GetExtensionTypes( t ) );

			return extensionTypes.ContainsKey( t )
				? types.Union( extensionTypes[ t ] ).ToList()
				: types.ToList();
		}

		/// <summary>
		/// Determines if a conversion exists from fromType to toType at the specified narrowing level.
		/// toNotNullable is true if the target variable doesn't allow null values.
        /// It is necessary here to signal all the conversions that we support e.g using an ImplicitConversionMethod,
        /// because otherwise a conversion error (e.g. ArgumentTypeException) is raised early in overload resolution. Saying yes
        /// results in a call to ConvertExpression below, where we specify the actual means of conversion 
		/// </summary>
		/// <param name="fromType"></param>
		/// <param name="toType"></param>
		/// <param name="toNotNullable"></param>
		/// <param name="level"></param>
		/// <returns></returns>
		public override bool CanConvertFrom( Type fromType, Type toType, bool toNotNullable, NarrowingLevel level )
		{
            if ( fromType == typeof( DynamicNull ) ) return true;
			if ( fromType == toType || toType.IsAssignableFrom( fromType ) ) return true;
			if ( CompilerHelpers.HasTypeConverter( fromType, toType ) ) return true;

			var fromCode = Type.GetTypeCode( fromType );
			var toCode = Type.GetTypeCode( toType );

			if ( fromCode == TypeCode.Int32
				&& ( toCode == TypeCode.Int64 || toCode == TypeCode.Double || toCode == TypeCode.Decimal ) )
				return true;

			if ( fromCode == TypeCode.Double
				&& toCode == TypeCode.Decimal )
				return true;

            // Supported in Int32Ops and DecimalOps
            if (toCode == TypeCode.Decimal && fromCode == TypeCode.Boolean ||
                toCode == TypeCode.Boolean && fromCode == TypeCode.Decimal ||
                toCode == TypeCode.Int32 && fromCode == TypeCode.Boolean ||
                toCode == TypeCode.Boolean && fromCode == TypeCode.Int32)
                return true;

			return ( fromCode == TypeCode.String || toCode == TypeCode.String );
		}

		/// <summary>
		/// Provides ordering for two parameter types if there is no conversion between the two parameter types.
		/// </summary>
		/// <param name="t1"></param>
		/// <param name="t2"></param>
		/// <returns></returns>
		public override Candidate PreferConvert( Type t1, Type t2 )
		{
			var t1Code = Type.GetTypeCode( t1 );
			var t2Code = Type.GetTypeCode( t2 );

			return ( t1Code > t2Code )
				? Candidate.One
				: ( t1Code < t2Code ) ? Candidate.Two : Candidate.Equivalent;
		}

		/// <summary>
		/// Converts the provided expression to the given type.  The expression is safe to evaluate multiple times.
        /// This is called to determine what type of conversion is needed.  Where possible, we defer to underlying types.
        /// Otherwise, return a dynamic conversion that will later result in the invocation of our own ImplicitConversionMethod's
		/// </summary>
		/// <param name="expr"></param>
		/// <param name="toType"></param>
		/// <param name="kind"></param>
		/// <param name="factory"></param>
		/// <returns></returns>
		public override Expression ConvertExpression( Expression expr, Type toType, ConversionResultKind kind, OverloadResolverFactory factory )
		{
			var exprType = expr.Type;

			if ( toType == typeof( object ) )
			{
				return ( exprType.IsValueType )
					? Utils.Convert( expr, toType )
					: expr;
			}

			if ( toType.IsAssignableFrom( exprType ) )
			{
				return expr;
			}

			var visType = CompilerHelpers.GetVisibleType( toType );

            // Wrap the sub-expression to be converted in a dynamic conversion expression
            return Expression.Dynamic(
                new ExcelConversionBinder( this, visType, kind ),
                visType,
                expr );
		}
	}
}